#include "ResourceFactory.h"
#include "Exception.h"
#include <algorithm>

NS_B2D_BEGIN

ResourcePtr BaseResourceFactory::findResource(const StringId& name) const
{
	std::lock_guard<std::mutex> lock(mCacheMutex);
	const auto it = mCache.find(name);
	// Only return resource if loaded
	if(it == mCache.end() || it->second.state != ResourceInfo::LOADED)
		return ResourcePtr();
	return it->second.resource;
}

void BaseResourceFactory::_addResourceToCache(const ResourcePtr& r)
{
	if(!r) return;

	std::lock_guard<std::mutex> lock(mCacheMutex);
	ResourceInfo rinfo = { r, ResourceInfo::LOADED };
	mCache[r->getName()] = std::move(rinfo);
}

void BaseResourceFactory::_removeResourceFromCache(const StringId& id)
{
	std::lock_guard<std::mutex> lock(mCacheMutex);
	const auto it = mCache.find(id);
	if( it != mCache.end() )
		mCache.erase(it);
}

ResourcePtr BaseResourceFactory::_createEmpty(const string& name, bool addToCache)
{
	auto r = createEmpty();
	StringId id = name;
	r->setName(id);

	if( addToCache )
	{
		// If adding to cache lock it and add the resource
		// we mark the resource as loaded because there is no work to do
		std::lock_guard<std::mutex> lock(mCacheMutex);
		ResourceInfo rinfo = { r, ResourceInfo::LOADED };
		mCache[name] = std::move(rinfo);
	}
	return r;
}

ResourcePtr BaseResourceFactory::_createFromFile(const string& name, bool addToCache)
{
	auto r = createEmpty();
	StringId id = name;
	r->setName(id);
	if( addToCache )
	{
		// If adding to cache lock it and add the resource
		// mark as loading
		std::lock_guard<std::mutex> lock(mCacheMutex);
		ResourceInfo rinfo = { r, ResourceInfo::LOADING };
		mCache[name] = std::move(rinfo);
	}

	// Load resource
	try
	{
		// Get stream
		IFileSystem& fs = *GetContext().getInstance<IFileSystem>();
		StreamPtr stream = fs.getStream(name);
		if(!stream) B2D_THROW(Exception::IO_EXCEPTION, "Could not open \""+name+"\"");

		// Load and set loaded
		r->load(stream);
		auto& rinfo = mCache[r->getName()];
		rinfo.state = ResourceInfo::LOADED;
	}
	catch(...)
	{
		// Set state to failed, this is important to avoid an infinite loop
		// if another thread is waiting for this resource to load.
		auto& rinfo = mCache[r->getName()];
		rinfo.state = (char)ResourceInfo::FAILED;
		//Remove bad resource from cache
		_removeResourceFromCache(r->getName());
		throw;
	}
	
	return r;
}

ResourcePtr BaseResourceFactory::_getOrCreateFromFile(const string& path)
{
	ResourcePtr r;
	{
		std::lock_guard<std::mutex> lock(mCacheMutex);
		const auto it = mCache.find(path);

		// Resource exists & hasnt failed, this means its either loaded or
		// loading so simply wait until loaded or fails
		if( it != mCache.end() && it->second.state != ResourceInfo::FAILED)
		{
			const auto& rinfo = it->second;
			while(rinfo.state == ResourceInfo::LOADING);
			if(rinfo.state == ResourceInfo::LOADED) r = rinfo.resource;
		}
	}

	// If r exists return it, otherwise loaded it
	return (r.get() == nullptr ? _createFromFile(path, true) : r);
}

void BaseResourceFactory::_clearCache(void)
{
	std::lock_guard<std::mutex> lock(mCacheMutex);
	mCache.clear();
}

void BaseResourceFactory::_clearUnreferenced(void)
{
	std::lock_guard<std::mutex> lock(mCacheMutex);
	auto it = mCache.begin();
	const auto end = mCache.end();
	while( it != end )
	{
		it = (it->second.resource.unique() ? mCache.erase(it) : ++it);
	}
}

NS_B2D_END